#Libraries-------
# Packages you want
pkgs <- c(
  "tidyverse", "broom", "ggplot2", "scales", "pbmcapply",
  "lmtest", "sandwich", "lmerTest", "fixest", "gt",
  "modelsummary", "this.path"
)

# Install any that are missing (with dependencies) and set CRAN if needed
missing <- pkgs[!vapply(pkgs, requireNamespace, logical(1), quietly = TRUE)]
if (length(missing)) {
  if (is.null(getOption("repos")) || is.na(getOption("repos")["CRAN"]) || getOption("repos")["CRAN"] == "") {
    options(repos = c(CRAN = "https://cloud.r-project.org"))
  }
  install.packages(missing, dependencies = TRUE)
}

# Load all; report any that still fail
loaded <- vapply(
  pkgs,
  function(p) {
    tryCatch({ library(p, character.only = TRUE); TRUE },
             error = function(e) { message(sprintf("Failed to load %s: %s", p, conditionMessage(e))); FALSE })
  },
  logical(1)
)

if (!all(loaded)) {
  stop(sprintf("Packages failed to load: %s", paste(pkgs[!loaded], collapse = ", ")))
}

rm(loaded, missing, pkgs)

# Helpers -----------------------------------------------------------------
prepare_summary <- function(df, label = NULL, split_year_rows = FALSE) {
  judges <- df

  base <- judges %>%
    group_by(experience) %>%
    summarise(
      n            = comma(n()),
      judges = n_distinct(judge),
      male = n_distinct(judge[gender_judge == "Male"]),
      gender_male  = paste0(round(male/judges*100), "%"),
      judges       = comma(n_distinct(judge)),
      .groups = "drop"
    ) %>%
    select(-male) %>%
    pivot_longer(-experience, names_to = "Characteristic", values_to = "Value")

  if (!split_year_rows) {
    years <- judges %>%
      group_by(experience) %>%
      summarise(
        yr_bar   = sprintf("{%d} [%d] ('%02d–'%02d)",
                           round(mean(yr.bar,   na.rm = TRUE)),
                           round(median(yr.bar,   na.rm = TRUE)),
                           as.integer(min(yr.bar,   na.rm = TRUE)) %% 100,
                           as.integer(max(yr.bar,   na.rm = TRUE)) %% 100),
        yr_start = sprintf("{%d} [%d] ('%02d–'%02d)",
                           round(mean(yr.start, na.rm = TRUE)),
                           round(median(yr.start, na.rm = TRUE)),
                           as.integer(min(yr.start, na.rm = TRUE)) %% 100,
                           as.integer(max(yr.start, na.rm = TRUE)) %% 100),
        .groups = "drop"
      ) %>%
      pivot_longer(-experience, names_to = "Characteristic", values_to = "Value")
  } else {
    years <- judges %>%
      group_by(experience) %>%
      summarise(
        yr_bar_mean     = sprintf("%d", round(mean(yr.bar,   na.rm = TRUE))),
        yr_bar_median   = sprintf("%d", round(median(yr.bar,   na.rm = TRUE))),
        yr_bar_range    = sprintf("'%02d–'%02d",
                                  as.integer(min(yr.bar,   na.rm = TRUE)) %% 100,
                                  as.integer(max(yr.bar,   na.rm = TRUE)) %% 100),
        yr_start_mean   = sprintf("%d", round(mean(yr.start, na.rm = TRUE))),
        yr_start_median = sprintf("%d", round(median(yr.start, na.rm = TRUE))),
        yr_start_range  = sprintf("'%02d–'%02d",
                                  as.integer(min(yr.start, na.rm = TRUE)) %% 100,
                                  as.integer(max(yr.start, na.rm = TRUE)) %% 100),
        .groups = "drop"
      ) %>%
      pivot_longer(-experience, names_to = "Characteristic", values_to = "Value") %>%
      mutate(
        Characteristic = recode(
          Characteristic,
          "yr_bar_mean"     = "Yr Admitted: Mean",
          "yr_bar_median"   = "Yr Admitted: Median",
          "yr_bar_range"    = "Yr Admitted: Min–Max",
          "yr_start_mean"   = "Yr Became Judge: Mean",
          "yr_start_median" = "Yr Became Judge: Median",
          "yr_start_range"  = "Yr Became Judge: Min–Max"
        )
      )
  }

  out <- bind_rows(base, years) %>%
    pivot_wider(names_from = experience, values_from = Value)

  out$Characteristic <- recode(
    out$Characteristic,
    "n" = "Cases",
    "judges" = "Judges",
    "gender_male" = "Male",
    .default = out$Characteristic
  )

  out
}

format_tex_n_row_thousands <- function(tex_string) {
  if (is.null(tex_string)) return(tex_string)
  s <- as.character(tex_string)
  lines <- strsplit(s, "\n", fixed = FALSE)[[1]]
  if (!length(lines)) return(s)

  for (i in seq_along(lines)) {
    li <- lines[i]
    if (grepl("^\\s*(N|Num\\.?\\s*Obs\\.?|Num\\.?\\s*Observations?|Observations)\\s*&", li, perl = TRUE, ignore.case = TRUE)) {
      parts <- strsplit(li, "&", fixed = TRUE)[[1]]
      if (length(parts) <= 1) next
      # Reformat numeric cells (all cells after the first label), preserving spacing and \\\ row terminators
      for (j in 2:length(parts)) {
        cell <- parts[j]
        m <- regexpr("[0-9]{4,}", cell, perl = TRUE)
        if (m != -1) {
          digits <- substring(cell, m, m + attr(m, "match.length") - 1)
          digits_num <- suppressWarnings(as.numeric(gsub(",", "", digits)))
          if (!is.na(digits_num)) {
            fmt <- prettyNum(digits_num, big.mark = ",", preserve.width = "none")
            before <- if (m > 1) substring(cell, 1, m - 1) else ""
            after  <- substring(cell, m + attr(m, "match.length"))
            parts[j] <- paste0(before, fmt, after)
          }
        }
      }
      lines[i] <- paste(parts, collapse = "&")
    }
  }
  paste(lines, collapse = "\n")
}

tex_insert_before_end_tabular <- function(tex, insert_text) {
  if (is.null(tex)) return(tex)
  s <- as.character(tex)
  if (length(s) > 1) s <- paste(s, collapse = "\n")
  # Find the last \end{tabular} and remove everything from it to the end
  m <- gregexpr("\\\\end\\{tabular\\}", s, perl = TRUE)[[1]]
  if (length(m) > 0 && m[1] != -1) {
    last_start <- tail(m, 1)
    s_head <- substring(s, 1, last_start - 1)
  } else {
    s_head <- s
  }
  s_head <- sub("\\\\s*$", "", s_head, perl = TRUE)
  # Remove any standalone \hline lines from the tabular content
  s_head <- gsub("(?m)^[\t ]*\\\\hline[\t ]*(\r?\n)?", "", s_head, perl = TRUE)
  if (is.null(insert_text) || length(insert_text) == 0) {
    return(s_head)
  }
  # If insert_text includes a \parbox, add a small vertical space at the start of its body
  insert_text <- gsub("(\\\\parbox\\{[^}]+\\}\\{)", "\\1\\\\vspace{0.9em}", insert_text, perl = TRUE)
  paste0(s_head, "\n", insert_text, "\n\\end{tabular}")
}

# Strip any content after the last \end{tabular}
tex_strip_tail_after_last_end_tabular <- function(tex) {
  if (is.null(tex)) return(tex)
  s <- as.character(tex)
  if (length(s) > 1) s <- paste(s, collapse = "\n")
  m <- gregexpr("\\\\end\\{tabular\\}", s, perl = TRUE)[[1]]
  if (length(m) > 0 && m[1] != -1) {
    last_start <- tail(m, 1)
    s <- substring(s, 1, last_start - 1)
    s <- sub("\\\\s*$", "", s, perl = TRUE)
  }
  s
}

fmt_pct <- function(x) {
  ifelse(is.na(x), "", paste0(formatC(x, format = "f", digits = 0), "\\%"))
}



# Globals -----------------------------------------------------------------
# Standard footnote text for regression tables
# Placeholder XXXXX is replaced with column count in individual table functions
note_text_global <<- paste0(
  "\\multicolumn{XXXXX}{l}{\\parbox{0.9\\textwidth}{\\footnotesize\\raggedright ",
  "Standard errors clustered by judge in parentheses. \\\\ ",
  "All models include defendant-, case-, and arraignment-administration fixed effects. \\\\ ",
  "Signif.: $^{\\dagger}p<0.10$, $^{*}p<0.05$, $^{**}p<0.01$, $^{***}p<0.001$.}}"
)

# Output directory for LaTeX tables
tex_dir <<- file.path(paste0(dirname(this.path()), "/outputs"), "tex")
if (!dir.exists(tex_dir)) dir.create(tex_dir, recursive = TRUE, showWarnings = FALSE)

# Goodness-of-fit mapping for modelsummary tables
# Controls which model statistics are displayed and their formatting
gmap <<- tribble(
  ~raw,         ~clean, ~fmt,
  "nobs",       "N",    0,
  "r.squared",  "R²",   3,
  "rmse",       "RMSE", 3
)


# Figures and Tables ------------------------------------------------------
fig_1 <- function(detentiondata, out_base = paste0(dirname(this.path()), "/outputs")) {
  col_single_in  <- 3.35
  col_double_in  <- 6.69
  dpi_out        <- 600

  base_family <- "Times"
  out_dir   <- dirname(out_base)
  base_name <- basename(out_base)
  tiff_dir  <- file.path(out_dir, "outputs/tiff")
  eps_dir   <- file.path(out_dir,  "outputs/eps")
  if (!dir.exists(tiff_dir)) dir.create(tiff_dir, recursive = TRUE, showWarnings = FALSE)
  if (!dir.exists(eps_dir))  dir.create(eps_dir,  recursive = TRUE, showWarnings = FALSE)

  results <- detentiondata %>%
     select(-experience) %>%
    left_join(readRDS(paste0(dirname(this.path()), "/data/judgeinfo.RDS")) %>% select(judge, detailed_experience), by = "judge") %>%
   mutate(
      detailed_experience = forcats::fct_drop(detailed_experience),
      parent_experience   = case_when(
        detailed_experience %in% c("Prosecutor", "AUSA", "Police") ~ "Law Enforcement",
        TRUE ~ as.character(detailed_experience)
      ),
      parent_experience   = factor(parent_experience,
                                   levels = c("None", "Law Enforcement", "Legal Services", "Both"))
    ) %>%
    group_by(parent_experience, detailed_experience) %>%
    summarise(total_judges = n_distinct(judge), .groups = "drop")

  fill_levels <- c("None","AUSA","Both","Legal Services","Police","Prosecutor")
  results$detailed_experience <- factor(results$detailed_experience, levels = fill_levels)
  greys <- grey_pal(start = 0.85, end = 0.15)(length(fill_levels))
  names(greys) <- fill_levels

  p <- ggplot2::ggplot(results,
                       ggplot2::aes(x = parent_experience, y = total_judges, fill = detailed_experience)) +
    ggplot2::geom_col(width = 0.68, colour = "black", linewidth = 0.25) +
    ggplot2::geom_text(ggplot2::aes(label = comma(total_judges)),
                       position = ggplot2::position_stack(vjust = 0.5),
                       size = 2.5, colour = "white", family = base_family) +
    ggplot2::scale_fill_manual(values = greys, breaks = fill_levels, name = NULL) +
    ggplot2::scale_y_continuous(labels = comma, expand = ggplot2::expansion(mult = c(0, 0.05))) +
    ggplot2::labs(
      x = NULL, y = "Judges"
    ) +
    ggplot2::theme_bw(base_size = 9, base_family = base_family) +
    ggplot2::theme(
      plot.title       = ggplot2::element_text(face = "bold", hjust = 0, margin = ggplot2::margin(b = 4)),
      panel.grid.major = ggplot2::element_line(linewidth = 0.2, colour = "grey85"),
      panel.grid.minor = ggplot2::element_blank(),
      panel.border     = ggplot2::element_rect(colour = "black", linewidth = 0.4),
      axis.ticks       = ggplot2::element_line(linewidth = 0.3, colour = "black"),
      axis.text.x      = ggplot2::element_text(angle = 45, hjust = 0.5, vjust = 0.5, margin = ggplot2::margin(t = 2)),
      axis.text.y      = ggplot2::element_text(margin = ggplot2::margin(r = 2)),
      legend.key.size  = ggplot2::unit(2, "mm"),
      legend.box.margin= ggplot2::margin(l = 2),
      legend.position  = "right"
    )


  grDevices::tiff(filename = file.path(tiff_dir, paste0(base_name, "Figure1_singlecol.tiff")),
                  width = col_single_in, height = 2.6, units = "in",
                  res = dpi_out, compression = "lzw")
  print(p)
  grDevices::dev.off()

  try({
    grDevices::cairo_ps(filename = file.path(eps_dir, paste0(base_name, "Figure1_singlecol.eps")),
                        width = col_single_in, height = 2.6,
                        onefile = FALSE, fallback_resolution = dpi_out)
    print(p)
    grDevices::dev.off()
  }, silent = TRUE)


  try({
    grDevices::tiff(filename = file.path(tiff_dir, paste0(base_name, "Figure1_doublecol.tiff")),
                    width = col_double_in, height = 2.6, units = "in",
                    res = dpi_out, compression = "lzw")
    print(p)
    grDevices::dev.off()
  }, silent = TRUE)

  try({
    grDevices::cairo_ps(filename = file.path(eps_dir, paste0(base_name, "Figure1_doublecol.eps")),
                        width = col_double_in, height = 2.6,
                        onefile = FALSE, fallback_resolution = dpi_out)
    print(p)
    grDevices::dev.off()
  }, silent = TRUE)
}
fig_A1<- function(baildata,
                 out_base = paste0(dirname(this.path()), "/outputs"),
                 bail_col = "cash.bail") {

  col_single_in <- 3.35
  col_double_in <- 6.69
  dpi_out       <- 600
  base_family   <- "Times"

  out_dir   <- dirname(out_base)
  base_name <- basename(out_base)
  tiff_dir  <- file.path(out_dir, "outputs/tiff")
  eps_dir   <- file.path(out_dir,  "outputs/eps")
  if (!dir.exists(tiff_dir)) dir.create(tiff_dir, recursive = TRUE, showWarnings = FALSE)
  if (!dir.exists(eps_dir))  dir.create(eps_dir,  recursive = TRUE, showWarnings = FALSE)

  if (!bail_col %in% names(baildata)) {
    stop(sprintf("Column '%s' not found in 'baildata'.", bail_col))
  }

  x <- suppressWarnings(as.numeric(baildata[[bail_col]]))

  breaks <- c(-Inf, 0, 5000, 10000, 25000, 50000, 100000, Inf)
  labels <- c("$0",
              "$1–$5,000", "$5,001–$10,000", "$10,001–$25,000",
              "$25,001-$50,000",
              "$50,001–$100,000", "$100,001+")

  df <- data.frame(cash = x) |>
    mutate(bin = cut(cash, breaks = breaks, labels = labels, right = TRUE)) |>
    filter(!is.na(bin)) |>
    count(bin, name = "n") |>
    mutate(p = n / sum(n)) |>
    mutate(bin = factor(
      bin,
      levels = labels
    ))

  p <- ggplot2::ggplot(df, ggplot2::aes(x = bin, y = p)) +
    ggplot2::geom_col(width = 0.82, fill = "grey65", colour = "black", linewidth = 0.25) +
    ggplot2::geom_text(
      ggplot2::aes(label = percent(p, accuracy = 1)),
      vjust = -0.2, size = 2.4, family = base_family
    ) +
    ggplot2::scale_y_continuous(labels = percent_format(accuracy = 1),
                                expand = ggplot2::expansion(mult = c(0, 0.08))) +
    ggplot2::labs(
      x = "Cash bail (binned, USD)",
      y = "Share of cases"
    ) +
    ggplot2::theme_bw(base_size = 9, base_family = base_family) +
    ggplot2::theme(
      plot.title       = ggplot2::element_text(face = "bold", hjust = 0, margin = ggplot2::margin(b = 4)),
      panel.grid.major = ggplot2::element_line(linewidth = 0.2, colour = "grey85"),
      panel.grid.minor = ggplot2::element_blank(),
      panel.border     = ggplot2::element_rect(colour = "black", linewidth = 0.4),
      axis.ticks       = ggplot2::element_line(linewidth = 0.3, colour = "black"),
      axis.text.x      = ggplot2::element_text(angle = 30, hjust = 1, vjust = 1, margin = ggplot2::margin(t = 2)),
      legend.position  = "none"
    )

  grDevices::tiff(filename = file.path(tiff_dir, paste0(base_name, "FigureA1_singlecol.tiff")),
                  width = col_single_in, height = 2.6, units = "in",
                  res = dpi_out, compression = "lzw")
  print(p)
  grDevices::dev.off()

  try({
    grDevices::cairo_ps(filename = file.path(eps_dir, paste0(base_name, "FigureA1_singlecol.eps")),
                        width = col_single_in, height = 2.6,
                        onefile = FALSE, fallback_resolution = dpi_out)
    print(p)
    grDevices::dev.off()
  }, silent = TRUE)

  try({
    grDevices::tiff(filename = file.path(tiff_dir, paste0(base_name, "FigureA1_doublecol.tiff")),
                    width = col_double_in, height = 2.6, units = "in",
                    res = dpi_out, compression = "lzw")
    print(p)
    grDevices::dev.off()
  }, silent = TRUE)

  try({
    grDevices::cairo_ps(filename = file.path(eps_dir, paste0(base_name, "FigureA1_doublecol.eps")),
                        width = col_double_in, height = 2.6,
                        onefile = FALSE, fallback_resolution = dpi_out)
    print(p)
    grDevices::dev.off()
  }, silent = TRUE)
}

table_1_2 <- function(data, filename) {

  rename_vector <- c(
    "n"                     = "Cases",
    "age"                   = "Age",
    "gender_male"           = "Male",
    "topseverity_violent"   = "VFO",
    "topseverity_nonviolent"= "NVFO",
    "topseverity_misdemeanor"= "Misd.",
    "supervision"           = "Under Supervision",
    "misd_convictions"      = "Misd. convictions",
    "nvfo_convictions"      = "NVFO convictions",
    "vfo_convictions"       = "VFO convictions",
    "vfo_pending"           = "Pending VFOs",
    "nvfo_pending"          = "Pending NVFOs",
    "misd_pending"          = "Pending Misd.",
    "custody_arrest"        = "Custody Arrest"
  )

  summary_table <- data %>%
    group_by(experience) %>%
    summarise(
      n = comma(n()),
      age = sprintf("{%.1f} [%d] (%d-%d)",
                    mean(age),
                    median(age),
                    min(age),
                    max(age)),
      gender_male = paste0(round(sum(gender_defendant == "Male")/n()*100), "%"),
      topseverity_violent = paste0(round(sum(topseverity == "VFO") /n()*100), "%"),
      topseverity_nonviolent = paste0(round(sum(topseverity == "NVFO") /n()*100), "%"),
      topseverity_misdemeanor = paste0(round(sum(topseverity == "Misdemeanor") /n()*100), "%"),
      supervision = paste0(round(sum(supervision == TRUE)/n()*100), "%"),
      misd_convictions = sprintf("{%.1f} [%d] (%d-%d)",
                                 mean(misd.convictions),
                                 median(misd.convictions),
                                 min(misd.convictions),
                                 max(misd.convictions)),
      nvfo_convictions = sprintf("{%.1f} [%d] (%d-%d)",
                                 mean(nvfo.convictions),
                                 median(nvfo.convictions),
                                 min(nvfo.convictions),
                                 max(nvfo.convictions)),
      vfo_convictions = sprintf("{%.1f} [%d] (%d-%d)",
                                mean(vfo.convictions),
                                median(vfo.convictions),
                                min(vfo.convictions),
                                max(vfo.convictions)),
      vfo_pending = paste0(round(sum(vfo.pending == TRUE) /n()*100), "%"),
      nvfo_pending = paste0(round(sum(nvfo.pending == TRUE) /n()*100), "%"),
      misd_pending = paste0(round(sum(misd.pending == TRUE) /n()*100), "%"),
      custody_arrest = paste0(round(sum(arresttype == "Custody") /n()*100), "%"),
    ) %>%
    mutate_all(as.character) %>%
    pivot_longer(!experience, names_to = "Characteristic", values_to = "Value") %>%
    mutate(
      Characteristic = ifelse(Characteristic %in% names(rename_vector),
                              rename_vector[Characteristic],
                              Characteristic)
    ) %>%
    pivot_wider(names_from = experience, values_from = Value)


  options(modelsummary_factory_latex = "kableExtra",
          modelsummary_format_numeric_latex = "plain")

  tab <- modelsummary::datasummary_df(summary_table, output = "latex_tabular", title = NULL)
  writeLines(
    as.character(tab),
    file.path(tex_dir, filename)
  )
}

table_3 <- function(detentiondata, baildata) {

  summary_detain  <- prepare_summary(detentiondata, split_year_rows = TRUE)
  summary_cash <- prepare_summary(baildata, split_year_rows = TRUE)

  combined_summary <- bind_rows(summary_detain, summary_cash) %>%
    arrange(Characteristic)

  exp_cols <- setdiff(names(combined_summary), "Characteristic")

  merged_summary <- combined_summary %>%
    group_by(Characteristic) %>%
    summarise(
      across(
        all_of(exp_cols),
        ~ {
          v <- as.character(.x)
          if (length(v) == 1L) v
          else if (identical(v[1], v[2])) paste0(v[1], "*")
          else paste(v[1], "/", v[2])
        }
      ),
      .groups = "drop"
    )

  rename_vector <- c(
    "n"        = "Cases",
    "judges"   = "Judges",
    "yr_bar"   = "Year Admitted to Bar",
    "yr_start" = "Year Became Judge",
    "gender_male" = "Male"
  )

  merged_summary <- merged_summary %>%
    mutate(
      Characteristic = ifelse(Characteristic %in% names(rename_vector),
                              rename_vector[Characteristic], Characteristic)
    )

    options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tab <- datasummary_df(merged_summary, output = "latex_tabular", title = NULL)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table3.tex")
  )

}

table_4 <- function(Q1, Q2, Q3, Q4, Q5, Q6, Q7) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  mods <- list(
    "Law Enforcement" = Q1,
    "Legal Services." = Q2,
    "Both"            = Q3,
    "None"            = Q4,
    "Prosecutor"      = Q5,
    "AUSA"            = Q6,
    "Police"          = Q7
  )

  wald_p <- vapply(mods, function(m) {
    out <- try(wald(m), silent = TRUE)
    if (inherits(out, "try-error") || is.null(out)) return(NA_real_)
    as.numeric(out[[2]])
  }, numeric(1))

  fmt_p <- function(x) ifelse(is.na(x), "", formatC(x, format = "f", digits = 3))

  out_df <- tibble::tibble(
    Model = names(wald_p),
    `Wald p-value` = fmt_p(wald_p)
  )

  tab <- modelsummary::datasummary_df(
    out_df,
    output = "latex_tabular",
    title  = NULL,
    escape = FALSE
  )

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table4.tex"))

}

table_5 <- function(detentiondata, baildata) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  table_data_detained <- detentiondata %>%
    group_by(experience) %>%
    summarize(
      judges_detain  = n_distinct(judge),
      cases_detain   = n(),
      percent_detain = round(sum(detained, na.rm = TRUE) / n() * 100),
      .groups = "drop"
    ) %>%
    arrange(desc(percent_detain))

  table_data_cash <- baildata %>%
    group_by(experience) %>%
    summarize(
      judges_cash = n_distinct(judge),
      cases_cash  = n(),
      mean_cash   = round(mean(cash.bail, na.rm = TRUE)),
      median_cash = suppressWarnings(median(cash.bail[cash.bail > 0], na.rm = TRUE)),
      .groups = "drop"
    )

  merged <- table_data_detained %>%
    left_join(table_data_cash, by = "experience") %>%
    select(
      `Background` = experience,
      `Judges`                  = judges_detain,
      `Cases`                   = cases_detain,
      `Percent Detained`                       = percent_detain,
      `Judges `                 = judges_cash,
      `Cases `                  = cases_cash,
      `Avg`                     = mean_cash,
      `Median`                  = median_cash
    )

  fmt_int    <- function(x) ifelse(is.na(x), "", formatC(x, format = "d", big.mark = ","))
  fmt_dollar <- function(x) ifelse(is.na(x), "", paste0("$", formatC(x, format = "d", big.mark = ",")))
  fmt_dollar <- function(x) ifelse(is.na(x), "", paste0("\\$", formatC(x, format = "d", big.mark = ",")))

  out_df <- merged %>%
    mutate(
      `Judges`  = fmt_int(`Judges`),
      `Cases`   = fmt_int(`Cases`),
      `Percent Detained`   = fmt_pct(`Percent Detained`),
      `Judges ` = fmt_int(`Judges `),
      `Cases `  = fmt_int(`Cases `),
      `Avg`     = fmt_dollar(`Avg`),
      `Median`  = fmt_dollar(`Median`)
    )

  tex <- datasummary_df(out_df, output = "latex_tabular", title = NULL, escape = FALSE)
  tex_str <- as.character(tex)

  spanners <- paste0(
    "\\\\multicolumn{1}{c}{} & \\\\multicolumn{3}{c}{Detained} & \\\\multicolumn{4}{c}{Cash} \\\\\\\\\n",
    "\\\\cline{2-4}\\\\cline{5-8}\n"
  )

  tab <- sub("\\\\hline\\s*\\n", spanners, tex_str)

  note_text <- paste0(
    "\\multicolumn{8}{l}{\\parbox{0.9\\textwidth}{\\footnotesize\\raggedright ",
    "Average cash bail includes \\$0 cases (released). ",
    "Median cash bail excludes \\$0 cases; including them yields \\$0 for all backgrounds. ",
    "}}"
  )

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table5.tex")
  )
}

table_6 <- function(model_detention, model_extensive, model_intensive, vcov_intensive) {

  options(
    modelsummary_factory_latex = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  experience_vars <- c("experienceBoth", "experienceLaw Enforcement", "experienceLegal Services")

  tab = modelsummary(
    list(
      "Detention" = model_detention,
      "Bail (Extensive)" = model_extensive,
      "Bail (Intensive)" = coeftest(model_intensive, vcov = vcov_intensive)
    ),
    stars = c('†' = .1, '*' = .05, '**' = .01, '***' = .001),
    coef_map = c(
      "experienceBoth" = "Experience: Both",
      "experienceLaw Enforcement" = "Experience: Law Enforcement",
      "experienceLegal Services" = "Experience: Legal Services",
      "yr.start"              = "Year became judge",
      "yr.bar"                = "Year admitted to bar",
      "gender_judgeMale"      = "Male judge"
    ),
    gof_map = gmap,
    output = "latex_tabular",
    title = NULL,
    fmt = 3,
    statistic = "std.error"
  )

  tab <- format_tex_n_row_thousands(tab)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table6.tex")
  )

}

table_7 <- function(model_detention_LE,
                    model_extensive_LE,
                    model_intensive_LE,
                    vcov_intensive_LE,
                    model_detention_LS,
                    model_extensive_LS,
                    model_intensive_LS,
                    vcov_intensive_LS) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "Detention"         = model_detention_LE,
      "Bail (Ext.)"  = model_extensive_LE,
      "Bail (Int.)"  = coeftest(model_intensive_LE, vcov = vcov_intensive_LE),

      "Detention "        = model_detention_LS,
      "Bail (Ext.) " = model_extensive_LS,
      "Bail (Int.) " = coeftest(model_intensive_LS, vcov = vcov_intensive_LS)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "law_enforcementTRUE" = "Law Enforcement",
      "legal_servicesTRUE"  = "Legal Services",
      "yr.start"            = "Year became judge",
      "yr.bar"              = "Year admitted to bar",
      "gender_judgeMale"    = "Male judge"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 3,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  spanners <- paste0(
    " & \\\\multicolumn{3}{c}{Law Enforcement vs. All Others} ",
    "& \\\\multicolumn{3}{c}{Legal Services vs. All Others} \\\\\\\\\n",
    "\\\\cline{2-4} \\\\cline{5-7}\n"
  )
  tex <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanners, "\\1\n"),
    tex,
    perl = TRUE
  )

  tab <- format_tex_n_row_thousands(tex)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table7.tex")
  )
}

table_8 <- function(model_detention_detailed,
                    model_extensive_detailed,
                    model_intensive_detailed,
                    vcov_intensive_detailed) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tab <- modelsummary(
    list(
      "Detention"        = model_detention_detailed,
      "Bail (Extensive)" = model_extensive_detailed,
      "Bail (Intensive)" = coeftest(model_intensive_detailed, vcov = vcov_intensive_detailed)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "detailed_experienceBoth"           = "Experience: Both",
      "detailed_experienceProsecutor"     = "Experience: State Prosecutor",
      "detailed_experienceAUSA"           = "Experience: Federal Prosecutor",
      "detailed_experiencePolice"         = "Experience: Police Department",
      "detailed_experienceLegal Services" = "Experience: Legal Services",
      "yr.start"                          = "Year became judge",
      "yr.bar"                            = "Year admitted to bar",
      "gender_judgeMale"                  = "Male judge"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 3,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  tab <- format_tex_n_row_thousands(tab)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table8.tex")
  )
}

table_9 <- function(model_detention_yrs1,
                    model_detention_yrs2,
                    model_extensive_yrs1,
                    model_extensive_yrs2,
                    model_intensive_yr1,
                    model_intensive_yr2,
                    vcov_intensive_yr1,
                    vcov_intensive_yr2) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tab <- modelsummary(
    list(
      "Detention"        = model_detention_yrs1,
      "Bail (Ext.)"      = model_extensive_yrs1,
      "Bail (Int.)"      = coeftest(model_intensive_yr1, vcov = vcov_intensive_yr1),

      "Detention "       = model_detention_yrs2,
      "Bail (Ext.) "     = model_extensive_yrs2,
      "Bail (Int.) "     = coeftest(model_intensive_yr2, vcov = vcov_intensive_yr2)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "years_prosecutor"      = "Years as prosecutor",
      "pros_year_group5-9"    = "5–9 yrs",
      "pros_year_group10-14"  = "10–14 yrs",
      "pros_year_group15-19"  = "15–19 yrs",
      "pros_year_group20+"    = "20+ yrs",
      "yr.start"              = "Year became judge",
      "yr.bar"                = "Year admitted to bar",
      "gender_judgeMale"      = "Male judge"
    ),
    coef_keep  = "years_prosecutor|pros_year_group",
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 3,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  spanners <- paste0(
    " & \\\\multicolumn{3}{c}{Years (numeric)} ",
    "& \\\\multicolumn{3}{c}{Years (categorical)} \\\\\\\\\n",
    "\\\\cline{2-4} \\\\cline{5-7}\n"
  )

  tab <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanners, "\\1\n"),
    tab,
    perl = TRUE
  )

  tab <- format_tex_n_row_thousands(tab)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table9.tex")
  )
}

table_10 <- function(
    model_detention,  model_extensive,  model_intensive,  vcov_intensive,
    model_detention_LE, model_extensive_LE, model_intensive_LE, vcov_intensive_LE,
    model_detention_LS, model_extensive_LS, model_intensive_LS, vcov_intensive_LS
) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "Det."        = model_detention,
      "Ext."      = model_extensive,
      "Int."      = coeftest(model_intensive,  vcov = vcov_intensive),

      "Det."       = model_detention_LE,
      "Ext."     = model_extensive_LE,
      "Int."     = coeftest(model_intensive_LE, vcov = vcov_intensive_LE),

      "Det."      = model_detention_LS,
      "Ext."    = model_extensive_LS,
      "Int."    = coeftest(model_intensive_LS, vcov = vcov_intensive_LS)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "experienceLaw Enforcement" = "Exp: Law Enf.",
      "experienceLegal Services"  = "Exp: Leg. Se.",
      "law_enforcementTRUE"       = "Exp: Law Enf.",
      "legal_servicesTRUE"        = "Exp: Leg. Se.",
      "yr.start"                  = "Yrs Judge",
      "yr.bar"                    = "Yrs Admit",
      "gender_judgeMale"          = "Male Judge"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 2,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  tex <- format_tex_n_row_thousands(tex)

  spanners <- paste0(
    " & \\\\multicolumn{3}{c}{Main} ",
    "& \\\\multicolumn{3}{c}{LE v All} ",
    "& \\\\multicolumn{3}{c}{LS v All} \\\\\\\\\n",
    "\\\\cline{2-4} \\\\cline{5-7} \\\\cline{8-10}\n"
  )

  tex <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanners, "\\1\n"),
    tex,
    perl = TRUE
  )
  tab <- format_tex_n_row_thousands(tex)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table10.tex")
  )
}

table_11 <- function(
    model_detention,  model_extensive,  model_intensive,  vcov_intensive,
    model_detention_LE, model_extensive_LE, model_intensive_LE, vcov_intensive_LE,
    model_detention_LS, model_extensive_LS, model_intensive_LS, vcov_intensive_LS
) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "Det."        = model_detention,
      "Ext."      = model_extensive,
      "Int."      = coeftest(model_intensive,  vcov = vcov_intensive),

      "Det."       = model_detention_LE,
      "Ext."     = model_extensive_LE,
      "Int."     = coeftest(model_intensive_LE, vcov = vcov_intensive_LE),

      "Det."      = model_detention_LS,
      "Ext."    = model_extensive_LS,
      "Int."    = coeftest(model_intensive_LS, vcov = vcov_intensive_LS)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "experienceLaw Enforcement" = "Exp: Law Enf.",
      "experienceLegal Services"  = "Exp: Leg. Se.",
      "law_enforcementTRUE"       = "Exp: Law Enf.",
      "legal_servicesTRUE"        = "Exp: Leg. Se.",
      "yr.start"                  = "Yrs Judge",
      "yr.bar"                    = "Yrs Admit",
      "gender_judgeMale"          = "Male Judge"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 2,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  tex <- format_tex_n_row_thousands(tex)

  spanners <- paste0(
    " & \\\\multicolumn{3}{c}{Main} ",
    "& \\\\multicolumn{3}{c}{LE v All} ",
    "& \\\\multicolumn{3}{c}{LS v All} \\\\\\\\\n",
    "\\\\cline{2-4} \\\\cline{5-7} \\\\cline{8-10}\n"
  )

  tex <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanners, "\\1\n"),
    tex,
    perl = TRUE
  )

  tab <- format_tex_n_row_thousands(tex)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "table11.tex")
  )
}

table_A1 <- function(model_detention_excluded,
                     model_extensive_excluded,
                     model_detention_excluded_50,
                     model_extensive_excluded_50) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "Detention"        = model_detention_excluded,
      "Bail (Ext.)"      = model_extensive_excluded,
      "Detention "       = model_detention_excluded_50,
      "Bail (Ext.) "     = model_extensive_excluded_50
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "experienceBoth"            = "Experience: Both",
      "experienceLaw Enforcement" = "Experience: Law Enforcement",
      "experienceLegal Services"  = "Experience: Legal Services",
      "yr.start"                  = "Judge start year",
      "yr.bar"                    = "Bar admission year",
      "gender_judgeMale"          = "Judge is male"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 4,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  spanners <- paste0(
    " & \\\\multicolumn{2}{c}{All judges} ",
    "& \\\\multicolumn{2}{c}{\\Judges w/ 50+ cases} \\\\\\\\\n",
    "\\\\cline{2-3} \\\\cline{4-5}\n"
  )
  tex <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanners, "\\1\n"),
    tex,
    perl = TRUE
  )

  tab <- format_tex_n_row_thousands(tex)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "tableA1.tex")
  )
}

table_A2 <- function(model_detention, model_extensive, model_intensive, vcov_intensive) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tab <- modelsummary(
    list(
      "Detention"        = model_detention,
      "Bail (Extensive)" = model_extensive,
      "Bail (Intensive)" = coeftest(model_intensive, vcov = vcov_intensive)
    ),
    stars     = c('†' = .1, '*' = .05, '**' = .01, '***' = .001),
    coef_map  = c(
      "experienceBoth"                        = "Experience: Both",
      "experienceLaw Enforcement"             = "Experience: Law Enforcement",
      "experienceLegal Services"              = "Experience: Legal Services",
      "yr.start"                              = "Year became judge",
      "yr.bar"                                = "Year admitted to bar",
      "gender_judgeMale"                      = "Male judge",
      "arresttypeCustody"                     = "Arrest type: Custody",
      "age"                                   = "Age (years)",
      "gender_defendantMale"                  = "Defendant male",
      "topseverityNVFO"                       = "Top severity: Nonviolent felony",
      "topseverityVFO"                        = "Top severity: Violent felony",
      "supervisionTRUE"                       = "Under supervision at arrest",
      "misd.convictions"                      = "Prior misdemeanor convictions",
      "nvfo.convictions"                      = "Prior nonviolent felony convictions",
      "vfo.convictions"                       = "Prior violent felony convictions",
      "vfo.pendingTRUE"                       = "Pending violent felony",
      "nvfo.pendingTRUE"                      = "Pending nonviolent felony",
      "misd.pendingTRUE"                      = "Pending misdemeanor"
    ),
    coef_omit = "arraign.*|chargec.*|offensel.*|county",
    gof_map   = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt       = 3,
    statistic = "std.error",
    file    = file.path(tex_dir, "tableA2.tex"),
    output  = "latex_tabular",
    title   = NULL
  )

  tab <- format_tex_n_row_thousands(tab)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "tableA2.tex")
  )
}


table_A3 <- function(model_detention_both, model_extensive_both, model_intensive_both,
                     vcov_intensive_both) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "Detention"        = model_detention_both,
      "Bail (Extensive)" = model_extensive_both,
      "Bail (Intensive)" = coeftest(model_intensive_both, vcov = vcov_intensive_both)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "experienceLaw Enforcement" = "Experience: Law Enforcement",
      "experienceLegal Services"  = "Experience: Legal Services",
      "yr.start"                  = "Year became judge",
      "yr.bar"                    = "Year admitted to bar",
      "gender_judgeMale"          = "Male judge"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 3,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )


  tab <- format_tex_n_row_thousands(tex)
  writeLines(
    as.character(tab),
    file.path(tex_dir, "tableA3.tex")
  )
}

table_A4 <- function(
    model_2022_2023,
    model_extensive_2022_2023,
    model_intensive_2022_2023,
    vcov_intensive_2022_2023) {

  options(
    modelsummary_factory_latex = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )
  experience_vars <- c("experienceBoth", "experienceLaw Enforcement", "experienceLegal Services")

  tab = modelsummary(
    list(
      "Detention" = model_2022_2023,
      "Bail (Extensive)" = model_extensive_2022_2023,
      "Bail (Intensive)" = coeftest(model_intensive_2022_2023, vcov = vcov_intensive_2022_2023)
    ),
    stars = c('†' = .1, '*' = .05, '**' = .01, '***' = .001),
    coef_map = c(
      "experienceBoth" = "Experience: Both",
      "experienceLaw Enforcement" = "Experience: Law Enforcement",
      "experienceLegal Services" = "Experience: Legal Services",
      "yr.start"              = "Year became judge",
      "yr.bar"                = "Year admitted to bar",
      "gender_judgeMale"      = "Male judge"
    ),
    gof_map = gmap,
    output = "latex_tabular",
    title = NULL,
    fmt = 3,
    statistic = "std.error"
  )


  tab <- format_tex_n_row_thousands(tab)

  writeLines(
    as.character(tab),
    file.path(tex_dir, "tableA4.tex")
  )

}

table_A5 <- function(model_detention,
                     model_logit,
                     model_extensive,
                     model_logit_int) {

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "LPM"   = model_detention,
      "Logit" = model_logit,
      "LPM "   = model_extensive,
      "Logit " = model_logit_int
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "experienceBoth"            = "Experience: Both",
      "experienceLaw Enforcement" = "Experience: Law Enforcement",
      "experienceLegal Services"  = "Experience: Legal Services"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 3,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  spanners <- paste0(
    " & \\\\multicolumn{2}{c}{Detention} ",
    "& \\\\multicolumn{2}{c}{Bail (Extensive)} \\\\\\\\\n",
    "\\\\cline{2-3} \\\\cline{4-5}\n"
  )

  tex <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanners, "\\1\n"),
    tex,
    perl = TRUE
  )

  tab <- format_tex_n_row_thousands(tex)
  writeLines(
    as.character(tab),
    file.path(tex_dir, "tableA5.tex")
  )
}

table_A6 <- function(model_intensive,
                     vcov_intensive,
                     model_step2_gamma,
                     model_logplus,
                     vcov_logplus) {

  vcov_gamma <- vcov(model_step2_gamma, cluster = "judge", type = "HC1")

  options(
    modelsummary_factory_latex        = "kableExtra",
    modelsummary_format_numeric_latex = "plain"
  )

  tex <- modelsummary(
    list(
      "Main"         = coeftest(model_intensive,   vcov = vcov_intensive),
      "Gamma GLM"    = coeftest(model_step2_gamma,   vcov = vcov_gamma),
      "LOGBAIL"  = coeftest(model_logplus,     vcov = vcov_logplus)
    ),
    stars      = c('†' = .10, '*' = .05, '**' = .01, '***' = .001),
    coef_map   = c(
      "experienceBoth"            = "Experience: Both",
      "experienceLaw Enforcement" = "Experience: Law Enforcement",
      "experienceLegal Services"  = "Experience: Legal Services",
      "yr.start"                  = "Year became judge",
      "yr.bar"                    = "Year admitted to bar",
      "gender_judgeMale"          = "Male judge"
    ),
    coef_omit  = "arraign.*|chargec.*|offensel.*|county",
    gof_map    = c("nobs", "r.squared", "adj.r.squared", "rmse"),
    fmt        = 3,
    statistic  = "std.error",
    estimate   = "{estimate} {stars}",
    output     = "latex_tabular",
    title      = NULL
  )

  spanner <- paste0(
    " & \\\\multicolumn{3}{c}{Bail (Intensive)} \\\\\\\\\n",
    "\\\\cline{2-4}\n"
  )
  tex <- sub(
    "\\\\hline\\s*(&[^\\n]+\\\\\\\\)\\s*\\\\hline",
    paste0(spanner, "\\1\n"),
    tex,
    perl = TRUE
  )

  tex <- gsub(
    "LOGBAIL",
    "$\\\\log(\\\\text{bail}+\\\\epsilon)$",
    tex
  )

  tab <- format_tex_n_row_thousands(tex)
  writeLines(
    as.character(tab),
    file.path(tex_dir, "tableA6.tex")
  )
}


get_judge_years <- function(df, judge_id) {
  years <- df |>
    filter(judge == judge_id) |>
    mutate(arraigndate = as.Date(arraigndate)) |>
    pull(arraigndate) |>
    lubridate::year() |>
    unique()

  sum(years %in% c(2020, 2021, 2022)) +
    0.5 * (2023 %in% years)
}
